package com.fast.fast.common.redis.util;

import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 锁工具类
 *
 * @author lyf
 * @date 2022/01/01 00:00 周六
 **/
@RequiredArgsConstructor
@Slf4j
public class LockUtils {

    private final RedisUtils redisUtils;

    /**
     * redis锁的key前缀
     */
    private static final String PREFIX = "lock-";

    /**
     * redis锁过期时间，防止死锁，单位秒
     */
    private static final long TIMEOUT = 10;

    /**
     * 基于redis的分布式锁--加锁
     *
     * @param key       键
     * @param requestId 值，请求的唯一编号
     * @return 是否成功
     */
    @SneakyThrows
    public boolean distributedLock(String key, String requestId) {
        if (StrUtil.isBlank(key) || StrUtil.isBlank(requestId)) {
            return false;
        }
        log.debug("正在设置redis锁...");
        // 当前重试次数
        int start = 1;
        // 最大重试次数
        int retried = 5;
        // 使用while循环，加入重试机制，能够不切换线程，保持循环竞争的阻塞状态
        while (true) {
            // 判断依据:当key不存在时,是否能set成功
            boolean absent = redisUtils.setIfAbsent(PREFIX + key, requestId, TIMEOUT);
            if (absent) {
                log.debug("设置redis锁成功");
                return true;
            }
            // 休眠时间
            int sleepTime = 500;
            log.debug("设置redis锁失败,{}毫秒后自动重试第{}次", sleepTime, start);
            Thread.sleep(sleepTime);
            start++;
            if (start > retried) {
                log.debug("设置redis锁失败,已达最大重试次数");
                return false;
            }
        }
    }

    /**
     * 基于redis的分布式锁--解锁
     *
     * @param key       键
     * @param requestId 请求的唯一编号
     * @return 是否成功
     */
    public boolean distributedUnlock(String key, String requestId) {
        String originalKey = PREFIX + key;
        if (StrUtil.isBlank(key) || StrUtil.isBlank(requestId)) {
            return false;
        }
        // 主动删除锁的时候，需要判断锁的编号是否和设置的一致，如果一致，则认为是自己上的锁，可以进行主动删除
        String value = (String) redisUtils.get(originalKey);
        if (requestId.equals(value)) {
            return redisUtils.delete(originalKey);
        }
        return false;
    }

    private final AtomicReference<Thread> cas = new AtomicReference<>();

    private int count;

    /**
     * 基于CAS实现的自旋锁--加锁
     */
    public void lock() {
        Thread thread = Thread.currentThread();
        log.debug("thread:{},come in", thread.getName());
        // 如果当前线程已经获取到了锁，count加1，然后返回
        if (thread == cas.get()) {
            count++;
            return;
        }
        // 如果没获取到锁，则通过CAS自旋
        while (!cas.compareAndSet(null, thread)) {

        }
    }

    /**
     * 基于CAS实现的自旋锁--解锁
     */
    public void unlock() {
        Thread thread = Thread.currentThread();
        if (thread == cas.get()) {
            // 当count大于0，说明当前线程多次获取了该锁，通过count减一来释放锁
            if (count > 0) {
                count--;
            } else {
                // 当count==0，可以将锁释放，这样就能保证获取锁的次数与释放锁的次数是一致的
                cas.compareAndSet(thread, null);
            }
        }
    }
}
